package com.dxy.app.widget.draw;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.dxy.app.R;
import java.util.ArrayList;
import java.util.List;

/**
 * 描 叙: 仿网易邮箱手势密码
 * 作 者: duxingyu
 * 时 间: 2017/8/7 17:04
 **/

public class GesturePasswordView extends View {

  public static final String TAG="TAG_GesturePasswordView";
  /**
   * 当前默认的未选中点的背景绘制色,绘制线条的颜色
   * */
  private int colorDef,colorLine;
  /**
   * 绘制圆点的画笔，绘制线条的画笔,阴影圆圈的画笔
   * */
  private Paint paint,pathPaint;
  /**
   * 控件宽高
   * */
  private int width,height;
  /**
   * 提示文字的尺寸
   * */
  private float textSize;
  /**
   * 控件需要绘制的log
   * */
  private Bitmap log;
  /**
   * 当前路径集合
   * */
  private Path path;
  /**
   * 还剩下的尝试次数
   * */
  private int tryTime;
  /**
   * log的区域范围
   * */
  private RectF rectLog;
  /**
   * 当前最后一个事件的坐标
   * */
  private PointF lastPoint;
  /**
   * 允许尝试的最大次数
   * */
  private int tryTimeCount;
  /**
   * 触发坐标的有效半径
   * */
  private float gestureRadius;
  /**
   * 手势密码
   * */
  private StringBuilder password;
  /**
   * 有效会指点的最少个数
   * */
  private int invalidatePointCount;
  /**
   * 所有点的集合
   * */
  private List<GestruePoint> points;
  /**
   * 圆点半径，线条宽度
   * */
  private float pointRadius,lineWidth;
  /**
   * 选中的点
   * */
  private List<GestruePoint> selectedPoint;
  /**
   * 绘制结果的相关监听器
   * */
  private GesturePasswordListener gestureListener;


  public GesturePasswordView(Context context) {
    super(context);
    init(null);
  }

  public GesturePasswordView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    TypedArray array =context.obtainStyledAttributes(attrs, R.styleable.GesturePasswordView);
    init(array);
  }

  public GesturePasswordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray array =context.obtainStyledAttributes(attrs, R.styleable.GesturePasswordView);
    init(array);
  }
  /**
   * 初始化所需的所有元素
   * */
  private void init(TypedArray array){
    int refrence;
    setBackgroundColor(Color.WHITE);
    if(array!=null){
      colorDef =array.getColor(R.styleable.GesturePasswordView_gesture_default_color,Color.BLUE);
      colorLine =array.getColor(R.styleable.GesturePasswordView_gesture_select_color,Color.RED);
      pointRadius =array.getDimensionPixelOffset(R.styleable.GesturePasswordView_gesture_point_radius,10);
      lineWidth =array.getDimensionPixelOffset(R.styleable.GesturePasswordView_gesture_line_width,5);
      invalidatePointCount =array.getInt(R.styleable.GesturePasswordView_gesture_validate_point_count,5);
      tryTime =tryTimeCount =array.getInt(R.styleable.GesturePasswordView_gesture_try_time,5);
      textSize =array.getDimensionPixelSize(R.styleable.GesturePasswordView_gesture_prompt_text_size,20);
      refrence =array.getResourceId(R.styleable.GesturePasswordView_gesture_log,R.mipmap.ic_launcher_round);
      array.recycle();
    }else{
      float density =getContext().getResources().getDisplayMetrics().density;
      pointRadius =5*density;
      lineWidth =pointRadius;
      colorDef = Color.BLUE;
      colorLine =Color.RED;
      invalidatePointCount =5;
      tryTime =tryTimeCount =5;
      textSize =density*10;
      refrence =R.mipmap.ic_launcher_round;
    }

    log = BitmapFactory.decodeResource(getResources(),refrence);

    gestureRadius =4*pointRadius;
    password =new StringBuilder();

    points =new ArrayList<>();
    selectedPoint =new ArrayList<>();

    paint =new Paint();
    paint.setColor(colorDef);
    paint.setAntiAlias(true);
    paint.setTextSize(textSize);

    pathPaint =new Paint();
    pathPaint.setColor(colorLine);
    pathPaint.setAntiAlias(true);
    pathPaint.setStrokeWidth(lineWidth);
    pathPaint.setStyle(Paint.Style.STROKE);

    path =new Path();

    lastPoint =new PointF();
  }

  @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    width =w;
    height =h;
    /**
     * 需要清除之间已经可能存在的坐标点
     * */
    points.clear();
    selectedPoint.clear();
    getPoints(width,height,points);
  }

  @Override protected void onDraw(Canvas canvas) {
    paint.setColor(colorDef);
    for(GestruePoint each:points){
      canvas.drawCircle(each.x,each.y,pointRadius,paint);
    }
    path.reset();
    if(selectedPoint.size()>0){
      path.moveTo(selectedPoint.get(0).x,selectedPoint.get(0).y);
      paint.setColor(colorLine);
      canvas.drawCircle(selectedPoint.get(0).x,selectedPoint.get(0).y,pointRadius,paint);
      for(int i=1;i<selectedPoint.size();i++){
        GestruePoint each =selectedPoint.get(i);
        path.lineTo(each.x,each.y);
        canvas.drawCircle(each.x,each.y,pointRadius,paint);
      }
      path.lineTo(lastPoint.x,lastPoint.y);
      canvas.drawPath(path,pathPaint);
    }
    drawMore(canvas);
  }
  /**
   * 可以根据需要绘制自定义的东西
   * */
  protected void drawMore(Canvas canvas){
    canvas.drawBitmap(log,null,rectLog,paint);
    if(isDrawPromptText){
      paint.setColor(Color.RED);
      String str ="手势密码错误,你还剩下"+tryTime+"次机会";
      float strWidth =paint.measureText(str);
      canvas.drawText(str,(width-strWidth)/2,rectLog.bottom-paint.ascent()*2,paint);
    }
  }
  /**
   * 是否绘制错误提醒文字
   * */
  private boolean isDrawPromptText =false;

  @Override public boolean onTouchEvent(MotionEvent event) {
    if(tryTime==0){
      return super.onTouchEvent(event);
    }
    switch (event.getAction()){
      case MotionEvent.ACTION_DOWN:
        isDrawPromptText =false;
      case MotionEvent.ACTION_MOVE:
        float x =event.getX();
        float y =event.getY();
        lastPoint.x =x;
        lastPoint.y =y;
        aroundPoint(lastPoint);
        break;
      case MotionEvent.ACTION_UP:
        resetPointState();
        break;
    }
    return true;
  }
  /**
   * 设置手势监听
   * */
  public void setGestureListener(GesturePasswordListener gestureListener){
    this.gestureListener =gestureListener;
  }
  /**
   * 每次绘制完成后需要将轨迹列表清零
   * */
  private void resetPointState(){
    if(selectedPoint.size()>=invalidatePointCount){//判断是否是有效绘制，如果是就判断密码是否正确。这儿需要更多的判断条件用于处理是否需要显示错误提示文字
      postDelayed(()->{
        if(gestureListener!=null){
          gestureListener.getGesturePassword(password.toString());
        }
        tryTime =tryTimeCount;//还原可绘制的次数
        resetState();
        invalidate();
      },500);
    }else{
      if(selectedPoint.size()>0){//只有在有绘制控制点但是又没有超过指定个数的时候才应该提示错误信息
        if(gestureListener!=null){
          gestureListener.uninvalidatePassword(invalidatePointCount);
        }
        isDrawPromptText =true;
        tryTime--;
      }
      resetState();
    }
    /**
     * 清除多余的绘制部分
     * */
    if(selectedPoint.size()>0){
      lastPoint.x =selectedPoint.get(selectedPoint.size()-1).x;
      lastPoint.y =selectedPoint.get(selectedPoint.size()-1).y;
    }
    invalidate();
    for(GestruePoint each:points){
      each.isSelected =false;
    }
  }
  /**
   * 手势操作完成后要还原状态
   * */
  private void resetState(){
    selectedPoint.clear();
    password.delete(0,password.length());
  }
  
  /**
   * 遍历计算当前点是否处于被选中状态
   * */
  private void aroundPoint(PointF last){
    for(GestruePoint each:points){
      if(selectedThisPoint(each,last)){
        password.append(each.index);
        each.isSelected =true;//标记当前点已经被选中
        selectedPoint.add(each);
        break;
      }
    }
    invalidate();
  }
  /**
   * 计算当前点是否被选中
   * */
  private boolean selectedThisPoint(GestruePoint point,PointF last){
    if(point.isSelected){//如果当前点已经选中了，那么就没有必要继续进行下面的运算了
      return false;
    }
    double distance =Math.sqrt(Math.pow(point.x-last.x,2)+Math.pow(point.y-last.y,2));
    if(distance<gestureRadius){
      return true;
    }else{
      return false;
    }
  }

  /**
   * 初始化各点的坐标,可以根据需要重新设置绘制点
   * @param width 控件宽
   * @param height 控件高
   * @param points 坐标列表
   * */
  protected void getPoints(float width,float height,List<GestruePoint> points){
    float distance =width/3.5f;
    float startX =(width-distance*2)/2;
    float startY =height-2*distance-startX*2;
    for(int i=0;i<9;i++){
      GestruePoint point =new GestruePoint();
      if(i%3==0){
        point.x =startX;
      }else if(i%3==1){
        point.x =startX+distance;
      }else{
        point.x =startX+2*distance;
      }
      if(i/3==0){
        point.y =startY;
      }else if(i/3==1){
        point.y =startY+distance;
      }else{
        point.y =startY+2*distance;
      }
      point.index =i;
      points.add(point);
    }
    GestruePoint last =new GestruePoint();
    last.index =9;
    last.x =startX+distance;
    last.y =points.get(points.size()-1).y+distance;
    points.add(last);

    float logWidth,logHeight;
    logWidth =log.getWidth();
    logHeight =log.getHeight();
    rectLog =new RectF((width-logWidth)/2,(startY-logHeight)/2-logHeight/2,(width+logWidth)/2,(startY+logHeight)/2-logHeight/2);
  }
  /**
   * 手势密码坐标描叙
   * */
  public static class GestruePoint{
    public float x;
    public float y;
    /**
     * 当前坐标的索引
     * */
    public int index;
    /**
     * 当前坐标是否被选中
     * */
    public boolean isSelected =false;
  }
}
